﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace LambdaManAi
{
    public class CompilerExpressionVisitor : ExpressionVisitor
    {
        private readonly LabelGenerator _labelGenerator;
        private Queue<Tuple<string, Expression>> _selectBranches = new Queue<Tuple<string, Expression>>();
        private Queue<Tuple<string, Expression>> _anonymousClosures = new Queue<Tuple<string, Expression>>();

        public CompilerExpressionVisitor(LabelGenerator labelGenerator)
        {
            _labelGenerator = labelGenerator;
        }

        protected override Expression VisitConstant(ConstantExpression node)
        {
            if (node.Type == typeof (bool))
            {
                int constantValue = (bool) node.Value ? 1 : 0;
                Console.WriteLine("LDC {0}", constantValue);
            }
            else
            {
                int constantValue = (int)node.Value;
                Console.WriteLine("LDC {0}", constantValue);    
            }
            return node;
        }

        protected override Expression VisitUnary(UnaryExpression node)
        {
            if (node.NodeType == ExpressionType.Not)
            {
                Console.WriteLine("LDC 1");
                Visit(node.Operand);
                Console.WriteLine("SUB");
                return node;
            }

            if (node.NodeType == ExpressionType.Negate)
            {
                Console.WriteLine("LDC 0");
                Visit(node.Operand);
                Console.WriteLine("SUB");
                return node;
            }

            return base.VisitUnary(node);
        }

        protected override Expression VisitBinary(BinaryExpression node)
        {
            if (node.NodeType == ExpressionType.AndAlso)
            {
                Visit(node.Left);
                var selectLabels = _labelGenerator.GenerateAndOperatorSelectLabels();
                string trueLabel = selectLabels.Item1;
                const string falseLabel = "select_zero_branch";
                Console.WriteLine("SEL {0} {1}", trueLabel, falseLabel);
                _selectBranches.Enqueue(new Tuple<string, Expression>(trueLabel, node.Right));
                return node;
            }

            if (node.NodeType == ExpressionType.OrElse)
            {
                Visit(node.Left);
                var selectLabels = _labelGenerator.GenerateOrOperatorSelectLabels();
                const string trueLabel = "select_one_branch";
                string falseLabel = selectLabels.Item2;
                Console.WriteLine("SEL {0} {1}", trueLabel, falseLabel);
                _selectBranches.Enqueue(new Tuple<string, Expression>(falseLabel, node.Right));
                return node;
            }

            if (node.NodeType == ExpressionType.ExclusiveOr)
            {
                Console.WriteLine("LDC 1");
                Visit(node.Left);
                Visit(node.Right);
                Console.WriteLine("SUB");
                Console.WriteLine("LDC 0");
                Console.WriteLine("CEQ");
                Console.WriteLine("SUB");
                return node;
            }

            if (node.NodeType == ExpressionType.NotEqual)
            {
                Console.WriteLine("LDC 1");
                Visit(node.Left);
                Visit(node.Right);
                Console.WriteLine("CEQ");
                Console.WriteLine("SUB");
                return node;
            }

            if (node.NodeType == ExpressionType.LessThan || node.NodeType == ExpressionType.LessThanOrEqual)
            {
                Visit(node.Right);
                Visit(node.Left);
                switch (node.NodeType)
                {
                    case ExpressionType.LessThan: Console.WriteLine("CGT"); break;
                    case ExpressionType.LessThanOrEqual: Console.WriteLine("CGTE"); break;
                    default:
                        throw new ArgumentOutOfRangeException("NodeType", string.Format("Operation '{0}' is not supported.", node.NodeType));
                }
                return node;
            }

            Visit(node.Left);
            Visit(node.Right);
            switch (node.NodeType)
            {
                case ExpressionType.Add: Console.WriteLine("ADD"); break;
                case ExpressionType.Subtract: Console.WriteLine("SUB"); break;
                case ExpressionType.Multiply: Console.WriteLine("MUL"); break;
                case ExpressionType.Divide: Console.WriteLine("DIV"); break;
                case ExpressionType.Equal: Console.WriteLine("CEQ"); break;
                case ExpressionType.GreaterThan: Console.WriteLine("CGT"); break;
                case ExpressionType.GreaterThanOrEqual: Console.WriteLine("CGTE"); break;
                    
                default:
                    throw new ArgumentOutOfRangeException("NodeType", string.Format("Operation '{0}' is not supported.", node.NodeType));
            }
            return node;
        }

        protected override Expression VisitConditional(ConditionalExpression node)
        {
            Visit(node.Test);
            Tuple<string, string> selectLabels = _labelGenerator.GenerateSelectLabels();
            string trueLabel = selectLabels.Item1;
            string falseLabel = selectLabels.Item2;
            Console.WriteLine("SEL {0} {1}", trueLabel, falseLabel);
            _selectBranches.Enqueue(new Tuple<string, Expression>(trueLabel, node.IfTrue));
            _selectBranches.Enqueue(new Tuple<string, Expression>(falseLabel, node.IfFalse));

            return node;
        }

        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            var methodInfo = node.Method;

            switch (methodInfo.Name)
            {
                case "GetParam": VisitGetParamMethodCall(node); break;
                case "GetEnv": VisitGetEnvMethodCall(node); break;
                case "GetLocal": VisitGetLocalMethodCall(node); break;
                case "Select": VisitSelectMethodCall(node); break;
                case "Pair": VisitPairMethodCall(node); break;
                case "Tuple": VisitTupleMethodCall(node); break;
                case "List": VisitListMethodCall(node); break;
                case "Closure": VisitClosureMethodCall(node); break;
                case "GetItem": VisitLibraryMethodCall(node, "_extract_list_elem", 2); break;
                case "GetLastTupleItem": VisitLibraryMethodCall(node, "_extract_tuple_last_elem", 2); break;
                case "Max2": VisitLibraryMethodCall(node, "_max", 2); break;
                case "Min2": VisitLibraryMethodCall(node, "_min", 2); break;
                case "FoldList": VisitLibraryMethodCall(node, "_fold_list", 3); break;
                case "MapList": VisitLibraryMethodCall(node, "_map_list", 2); break;
                case "get_Item":
                    if (node.Object.Type == typeof (Locals))
                        VisitLocalIndexer(node);
                    else
                        VisitUserMethodCall(node);
                    break;
                
                default: VisitUserMethodCall(node); break;
            }
            
            return node;
        }

        private void VisitGetParamMethodCall(MethodCallExpression node)
        {
            int parameterIndex = (int)((ConstantExpression)node.Arguments[1]).Value;
            Console.WriteLine("LD 0 {0}", parameterIndex);
        }

        private void VisitGetEnvMethodCall(MethodCallExpression node)
        {
            int frameIndex = (int)((ConstantExpression)node.Arguments[1]).Value;
            int parameterIndex = (int)((ConstantExpression)node.Arguments[2]).Value;
            Console.WriteLine("LD {0} {1}", frameIndex, parameterIndex);
        }

        private void VisitGetLocalMethodCall(MethodCallExpression node)
        {
            int parameterIndex = (int)((ConstantExpression)node.Arguments[1]).Value;
            Console.WriteLine("LD 0 {0}", parameterIndex);
        }

        private void VisitLocalIndexer(MethodCallExpression node)
        {
            int parameterIndex = (int)((ConstantExpression)node.Arguments[0]).Value;
            Console.WriteLine("LD 0 {0}", parameterIndex);
        }

        private void VisitLibraryMethodCall(MethodCallExpression node, string internalFuncName, int numArgs)
        {
            Visit(node.Arguments);
            Console.WriteLine("LDF {0}\nAP {1}", internalFuncName, numArgs);
        }

        private void VisitSelectMethodCall(MethodCallExpression node)
        {
            Visit(node.Arguments[0]);
            Tuple<string, string> selectLabels = _labelGenerator.GenerateSelectLabels();
            string trueLabel = selectLabels.Item1;
            string falseLabel = selectLabels.Item2;
            Console.WriteLine("SEL {0} {1}", trueLabel, falseLabel);
            _selectBranches.Enqueue(new Tuple<string, Expression>(trueLabel, node.Arguments[1]));
            _selectBranches.Enqueue(new Tuple<string, Expression>(falseLabel, node.Arguments[2]));
        }

        private void VisitPairMethodCall(MethodCallExpression node)
        {
            Visit(node.Arguments);
            Console.WriteLine("CONS");
        }

        private void VisitTupleMethodCall(MethodCallExpression node)
        {
            Visit(node.Arguments);
            int argumentsCount = ((NewArrayExpression)node.Arguments[0]).Expressions.Count;
            for (int i = 0; i < argumentsCount - 1; i++)
                Console.WriteLine("CONS");
        }

        private void VisitListMethodCall(MethodCallExpression node)
        {
            Visit(node.Arguments);
            Console.WriteLine("LDC 0 ; End of the list");
            int argumentsCount = ((NewArrayExpression)node.Arguments[0]).Expressions.Count;
            for (int i = 0; i < argumentsCount; i++)
                Console.WriteLine("CONS");
        }

        private void VisitClosureMethodCall(MethodCallExpression node)
        {
            if (node.Arguments[0] is UnaryExpression && ((UnaryExpression)node.Arguments[0]).Operand is MethodCallExpression)
            {
                string functionName = ((MethodInfo)((ConstantExpression)((MethodCallExpression)((UnaryExpression)node.Arguments[0]).Operand).Object).Value).Name;
                Console.WriteLine("LDF {0}", functionName);
            }
            else
            {
                // anonymous closure
                string closureName = _labelGenerator.GenerateAnonymousClosureLabel();
                _anonymousClosures.Enqueue(new Tuple<string, Expression>(closureName, node.Arguments[0]));
                Console.WriteLine("LDF {0}", closureName);
            }
        }

        private void VisitUserMethodCall(MethodCallExpression node)
        {
            Visit(node.Arguments);
            Console.WriteLine("LDF {0}", node.Method.Name);
            int argumentsCount = ((NewArrayExpression)node.Arguments[0]).Expressions.Count;
            Console.WriteLine("AP {0}", argumentsCount);
        }

        public void WriteSelectBranches()
        {
            bool done = false;
            while (!done)
            {
                done = true;
                while (_selectBranches.Count > 0)
                {
                    done = false;
                    Tuple<string, Expression> branch = _selectBranches.Dequeue();
                    string branchLabel = branch.Item1;
                    Expression branchNode = branch.Item2;
                    Console.WriteLine(branchLabel + ":");
                    Visit(branchNode);
                    Console.WriteLine("JOIN");
                    Console.WriteLine();
                }

                while (_anonymousClosures.Count > 0)
                {
                    done = false;
                    Tuple<string, Expression> branch = _anonymousClosures.Dequeue();
                    string closureLabel = branch.Item1;
                    Expression closureNode = branch.Item2;
                    Console.WriteLine(closureLabel + ":");
                    Visit(closureNode);
                    Console.WriteLine("RTN");
                    Console.WriteLine();
                }
            }
        }
    }
}
